#include <EB0/EB0.h>

namespace EB0
{
namespace Managers
{
	// Static Member
	AudioManager* AudioManager::mSingleton = 0;

#pragma region Constructor / Destructor
	// Constructor
	AudioManager::AudioManager()
	{
		// Nothing to do...
	}

	// Destructor
	AudioManager::~AudioManager()
	{
		// Destroy resources
		destroyAllMusic();
		destroyAllSounds();
		destroyAllSoundBuffers();

		// Reset values
		mSingleton = 0;
	}
#pragma endregion

#pragma region Singleton
	// Singleton Reference
	AudioManager& AudioManager::getSingleton()
	{
		return *getSingletonPtr();
	}

	// Singleton Pointer
	AudioManager* AudioManager::getSingletonPtr()
	{
		if (!mSingleton) mSingleton = new AudioManager();
		return mSingleton;
	}
#pragma endregion

#pragma region Music Management
	// Load a music file, or return the pointer if it already has been loaded
	sf::Music* AudioManager::createMusic(char* filename)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a music file already exists with the given filename
		MusicMap::iterator musicIter;
		musicIter = mMusic.find(filename);

		if (musicIter != mMusic.end())
			return musicIter->second; // return a previously existing pointer

		// Create new music
		mMusic[filename] = new sf::Music;
		mMusic[filename]->OpenFromFile(filename);

		return mMusic[filename];
	}

	// Unload a music file with a given pointer with reference
	bool AudioManager::destroyMusic(sf::Music*& music)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Scan through each music to determine if one exists with the given pointer
		MusicMap::iterator musicIter = mMusic.begin();
		bool found = false;

		while (musicIter != mMusic.end() && !found)
		{
			found = (musicIter->second == music);
			if (!found) musicIter++;
		}

		// Did the music exist? Let's delete it.
		if (found)
		{
			delete musicIter->second;
			musicIter->second = 0;
			mMusic.erase(musicIter);

			// Null the given pointer as well
			music = 0;
		}

		return found;
	}

	// Unload a music file with a given filename
	bool AudioManager::destroyMusic(char* filename)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a music exists with the given refname
		MusicMap::iterator musicIter;
		musicIter = mMusic.find(filename);

		if (musicIter != mMusic.end())
		{
			// Destroy the music
			delete musicIter->second;
			musicIter->second = 0;
			mMusic.erase(musicIter);

			return true;
		}

		return false;
	}

	// Unload all music
	void AudioManager::destroyAllMusic()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Destroy all music
		if (!mMusic.empty())
		{
			MusicMap::iterator musicIter;

			for (musicIter = mMusic.begin(); musicIter != mMusic.end(); musicIter++)
			{
				delete musicIter->second;
				musicIter->second = 0;
			}

			mMusic.clear();
		}
	}
#pragma endregion

#pragma region Sound Buffer Management
	// Load a sound buffer with a given filename
	sf::SoundBuffer* AudioManager::createSoundBuffer(char* filename)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a sound file already exists with the given filename
		SoundBufferMap::iterator soundBufferIter;
		soundBufferIter = mSoundBuffers.find(filename);

		if (soundBufferIter != mSoundBuffers.end())
			return soundBufferIter->second; // return a previously existing pointer

		// Create new SoundBuffer
		mSoundBuffers[filename] = new sf::SoundBuffer;
		mSoundBuffers[filename]->LoadFromFile(filename);

		return mSoundBuffers[filename];
	}

	// Unload a sound buffer with a given pointer
	bool AudioManager::destroySoundBuffer(sf::SoundBuffer*& soundBuffer)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Scan through each SoundBuffer to determine if one exists with the given pointer
		SoundBufferMap::iterator soundBufferIter = mSoundBuffers.begin();
		bool found = false;

		while (soundBufferIter != mSoundBuffers.end() && !found)
		{
			found = (soundBufferIter->second == soundBuffer);
			if (!found) soundBufferIter++;
		}

		// Did the SoundBuffer exist? Let's delete it.
		if (found)
		{
			delete soundBufferIter->second;
			soundBufferIter->second = 0;
			mSoundBuffers.erase(soundBufferIter);

			// Null the given pointer as well
			soundBuffer = 0;
		}

		return found;

		// Should all connected sounds also be destroyed lest they know longer function?
		// A feature to come...
	}

	// Unload a sound buffer with a given filename
	bool AudioManager::destroySoundBuffer(char* filename)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a SoundBuffer exists with the given refname
		SoundBufferMap::iterator soundBufferIter;
		soundBufferIter = mSoundBuffers.find(filename);

		if (soundBufferIter != mSoundBuffers.end())
		{
			// Delete sound buffer
			delete soundBufferIter->second;
			soundBufferIter->second = 0;
			mSoundBuffers.erase(soundBufferIter);

			return true;
		}

		return false;

		// Should all connected sounds also be destroyed lest they know longer function?
		// A feature to come...
	}

	// Unload all sound buffers
	void AudioManager::destroyAllSoundBuffers()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Destroy all sound buffers
		if (!mSoundBuffers.empty())
		{
			SoundBufferMap::iterator soundBufferIter;

			for (soundBufferIter = mSoundBuffers.begin(); soundBufferIter != mSoundBuffers.end(); soundBufferIter++)
			{
				delete soundBufferIter->second;
				soundBufferIter->second = 0;
			}

			mSoundBuffers.clear();
		}
	}
#pragma endregion

#pragma region Sounds
	// Create a sound from a given sound buffer
	sf::Sound* AudioManager::createSound(char* refname, sf::SoundBuffer* soundBuffer)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a sound exists with the given refname
		SoundMap::iterator soundIter;
		soundIter = mSounds.find(refname);

		if (soundIter != mSounds.end())
			return 0; // null pointer

		// Create a new sound
		mSounds[refname] = new sf::Sound((const sf::SoundBuffer&)(*soundBuffer));

		return mSounds[refname];
	}

	// Create a sound from a given filename
	sf::Sound* AudioManager::createSound(char* refname, char* filename)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a sound exists with the given refname
		SoundMap::iterator soundIter;
		soundIter = mSounds.find(refname);

		if (soundIter != mSounds.end())
			return 0; // null pointer

		// Create a new sound
		mSounds[refname] = new sf::Sound((const sf::SoundBuffer&)(*createSoundBuffer(filename)));

		return mSounds[refname];
	}

	// Destroy a sound with a given pointer
	bool AudioManager::destroySound(sf::Sound*& sound)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Scan through each Sound to determine if one exists with the given pointer
		SoundMap::iterator soundIter = mSounds.begin();
		bool found = false;

		while (soundIter != mSounds.end() && !found)
		{
			found = (soundIter->second == sound);
			if (!found) soundIter++;
		}

		// Did the Sound exist? Let's delete it.
		if (found)
		{
			delete soundIter->second;
			soundIter->second = 0;
			mSounds.erase(soundIter);

			// Null the given pointer as well
			sound = 0;
		}

		return found;
	}

	// Destroy a sound with a given refname
	bool AudioManager::destroySound(char* refname)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a Sound exists with the given refname
		SoundMap::iterator soundIter;
		soundIter = mSounds.find(refname);

		if (soundIter != mSounds.end())
		{
			// Delete sound
			delete soundIter->second;
			soundIter->second = 0;
			mSounds.erase(soundIter);

			return true;
		}

		return false;
	}

	// Fetch a sound with a given reference name
	sf::Sound* AudioManager::fetchSound(char* refname)
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Check if a sound exists with the given refname
		SoundMap::iterator soundIter;
		soundIter = mSounds.find(refname);

		if (soundIter == mSounds.end())
			return 0; // null pointer

		// Sound found!
		return soundIter->second;
	}

	// Unload all sounds
	void AudioManager::destroyAllSounds()
	{
		// Lock mutex; automatically unlocks when the function terminates
		sf::Lock lock(mMutex);

		// Destroy all sound buffers
		if (!mSounds.empty())
		{
			SoundMap::iterator soundIter;

			for (soundIter = mSounds.begin(); soundIter != mSounds.end(); soundIter++)
			{
				delete soundIter->second;
				soundIter->second = 0;
			}

			mSounds.clear();
		}
	}
#pragma endregion
} // namespace Managers
}